﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Writerous
{    
    /// <summary>
    /// Implements both encryption and decryption of strings using the PC1 encryption method.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716")]
    public sealed class PC1
    {
        private PC1()
        {
        }

        private const string _genericEncKey = "LOVETHEFERRET20";

        private static ushort _i;
        private static ushort _l;
        private static ushort _ax;
        private static ushort _bx;
        private static ushort _cx;
        private static ushort _dx;
        private static ushort _si;
        private static ushort _tmp;
        private static ushort _x1a2;
        private static ushort _res;
        private static ushort _inter;
        private static ushort _cfc;
        private static ushort _cfd;
        private static ushort[] _x1a0 = new ushort[8] { 0, 0, 0, 0, 0, 0, 0, 0 };

        private static char[] _cle = new char[16];
        private static char[] _cry = new char[33001];


        /// <summary>
        /// Private static method to code it.
        /// </summary>
        private static void Code()
        {
            _dx = (ushort)(_x1a2 + _i);
            _ax = _x1a0[_i];
            _cx = 0x015a;
            _bx = 0x4e35;
            _tmp = _ax;
            _ax = _si;
            _si = _tmp;
            _tmp = _ax;
            _ax = _dx;
            _dx = _tmp;
            if (_ax != 0)
                _ax *= _bx;
            _tmp = _ax;
            _ax = _cx;
            _cx = _tmp;
            if (_ax != 0)
            {
                _ax *= _si;
                _cx += _ax;
            }
            _tmp = _ax;
            _ax = _si;
            _si = _tmp;
            _ax *= _bx;
            _dx += _cx;
            ++_ax;
            _x1a2 = _dx;
            _x1a0[_i] = _ax;
            _res = (ushort)(_ax ^ _dx);
            ++_i;
        }

        /// <summary>
        /// Private static method to Assemble it.
        /// </summary>
        private static void Assemble()
        {
            _x1a0[0] = (ushort)((_cle[0] * 256) + _cle[1]);
            Code();
            _inter = _res;
            _x1a0[1] = (ushort)(_x1a0[0] ^ (((ushort)_cle[2] * 256) + (ushort)_cle[3]));
            Code();
            _inter = (ushort)(_inter ^ _res);
            _x1a0[2] = (ushort)(_x1a0[1] ^ (((ushort)_cle[4] * 256) + (ushort)_cle[5]));
            Code();
            _inter = (ushort)(_inter ^ _res);
            _x1a0[3] = (ushort)(_x1a0[2] ^ (((ushort)_cle[6] * 256) + (ushort)_cle[7]));
            Code();
            _inter = (ushort)(_inter ^ _res);
            _x1a0[4] = (ushort)(_x1a0[3] ^ (((ushort)_cle[8] * 256) + (ushort)_cle[9]));
            Code();
            _inter = (ushort)(_inter ^ _res);
            _x1a0[5] = (ushort)(_x1a0[4] ^ (((ushort)_cle[10] * 256) + (ushort)_cle[11]));
            Code();
            _inter = (ushort)(_inter ^ _res);
            _x1a0[6] = (ushort)(_x1a0[5] ^ (((ushort)_cle[12] * 256) + (ushort)_cle[13]));
            Code();
            _inter = (ushort)(_inter ^ _res);
            _x1a0[7] = (ushort)(_x1a0[6] ^ (((ushort)_cle[14] * 256) + (ushort)_cle[15]));
            Code();
            _inter = (ushort)(_inter ^ _res);
            _i = 0;
        }

        /// <summary>
        /// Carries out the work of the encryption.
        /// </summary>
        /// <param name="key">The character array of the key.</param>
        /// <param name="buffer">The character array of the value being encrypted.</param>
        /// <param name="bufferLength">The length of the array being encrypted.</param>
        private static void Encrypt(char[] key, char[] buffer, int bufferLength)
        {
            char rep = '\0';
            byte c = 0, d = 0, e = 0;
            ushort compte = 0, j = 0;

            Array.Clear(_cry, 0, _cry.GetLength(0));
            Array.Clear(_cle, 0, _cle.GetLength(0));
            Array.Copy(key, 0, _cle, 0, key.Length);
            _si = _x1a2 = _i = 0;

            for (j = 0; j < bufferLength; j++)
            {
                c = (byte)buffer[j];
                Assemble();
                _cfc = (ushort)(_inter >> 8);
                _cfd = (ushort)(_inter & 255);
                for (compte = 0; compte <= 15; compte++)
                {
                    _cle[compte] = (char)(_cle[compte] ^ c);
                }

                c ^= (byte)(_cfc ^ _cfd);
                d = (byte)(c >> 4);
                e = (byte)(c & 15);
                switch (d)
                {
                    case 0:
                        rep = 'a';
                        break;
                    case 1:
                        rep = 'b';
                        break;
                    case 2:
                        rep = 'c';
                        break;
                    case 3:
                        rep = 'd';
                        break;
                    case 4:
                        rep = 'e';
                        break;
                    case 5:
                        rep = 'f';
                        break;
                    case 6:
                        rep = 'g';
                        break;
                    case 7:
                        rep = 'h';
                        break;
                    case 8:
                        rep = 'i';
                        break;
                    case 9:
                        rep = 'j';
                        break;
                    case 10:
                        rep = 'k';
                        break;
                    case 11:
                        rep = 'l';
                        break;
                    case 12:
                        rep = 'm';
                        break;
                    case 13:
                        rep = 'n';
                        break;
                    case 14:
                        rep = 'o';
                        break;
                    case 15:
                        rep = 'p';
                        break;
                }
                _cry[j * 2] = rep;

                switch (e)
                {
                    case 0:
                        rep = 'a';
                        break;
                    case 1:
                        rep = 'b';
                        break;
                    case 2:
                        rep = 'c';
                        break;
                    case 3:
                        rep = 'd';
                        break;
                    case 4:
                        rep = 'e';
                        break;
                    case 5:
                        rep = 'f';
                        break;
                    case 6:
                        rep = 'g';
                        break;
                    case 7:
                        rep = 'h';
                        break;
                    case 8:
                        rep = 'i';
                        break;
                    case 9:
                        rep = 'j';
                        break;
                    case 10:
                        rep = 'k';
                        break;
                    case 11:
                        rep = 'l';
                        break;
                    case 12:
                        rep = 'm';
                        break;
                    case 13:
                        rep = 'n';
                        break;
                    case 14:
                        rep = 'o';
                        break;
                    case 15:
                        rep = 'p';
                        break;
                }
                _cry[j * 2 + 1] = rep;
            }
        }

        /// <summary>
        /// Carries out the work of the decryption.
        /// </summary>
        /// <param name="key">The character array of the key.</param>
        /// <param name="buffer">The character array of the encrypted string.</param>
        /// <param name="bufferLength">Length of the encrypted string.</param>
        private static void Decrypt(char[] key, char[] buffer, int bufferLength)
        {
            char rep = '\0';
            byte c = 0, d = 0, e = 0;
            ushort compte = 0, j = 0;

            Array.Clear(_cry, 0, _cry.GetLength(0));
            Array.Clear(_cle, 0, _cle.GetLength(0));
            Array.Copy(key, 0, _cle, 0, key.Length);
            _si = _x1a2 = _i = j = _l = 0;

            while (j < bufferLength - 1)
            {
                rep = buffer[j];

                switch (rep)
                {
                    case 'a':
                        d = 0;
                        break;
                    case 'b':
                        d = 1;
                        break;
                    case 'c':
                        d = 2;
                        break;
                    case 'd':
                        d = 3;
                        break;
                    case 'e':
                        d = 4;
                        break;
                    case 'f':
                        d = 5;
                        break;
                    case 'g':
                        d = 6;
                        break;
                    case 'h':
                        d = 7;
                        break;
                    case 'i':
                        d = 8;
                        break;
                    case 'j':
                        d = 9;
                        break;
                    case 'k':
                        d = 10;
                        break;
                    case 'l':
                        d = 11;
                        break;
                    case 'm':
                        d = 12;
                        break;
                    case 'n':
                        d = 13;
                        break;
                    case 'o':
                        d = 14;
                        break;
                    case 'p':
                        d = 15;
                        break;
                }
                d = (byte)(d << 4);
                ++j;

                rep = buffer[j];
                switch (rep)
                {
                    case 'a':
                        e = 0;
                        break;
                    case 'b':
                        e = 1;
                        break;
                    case 'c':
                        e = 2;
                        break;
                    case 'd':
                        e = 3;
                        break;
                    case 'e':
                        e = 4;
                        break;
                    case 'f':
                        e = 5;
                        break;
                    case 'g':
                        e = 6;
                        break;
                    case 'h':
                        e = 7;
                        break;
                    case 'i':
                        e = 8;
                        break;
                    case 'j':
                        e = 9;
                        break;
                    case 'k':
                        e = 10;
                        break;
                    case 'l':
                        e = 11;
                        break;
                    case 'm':
                        e = 12;
                        break;
                    case 'n':
                        e = 13;
                        break;
                    case 'o':
                        e = 14;
                        break;
                    case 'p':
                        e = 15;
                        break;
                }
                c = (byte)(d + e);
                Assemble();
                _cfc = (ushort)(_inter >> 8);
                _cfd = (ushort)(_inter & 255);
                c ^= (byte)(_cfc ^ _cfd);

                for (compte = 0; compte <= 15; compte++)
                {
                    _cle[compte] ^= (char)c;
                }

                _cry[_l] = (char)c;
                j++;
                _l++;
            }
        }


        /// <summary>
        /// Returns the encryption of the given string using the given key. 
        /// </summary>
        /// <param name="value">The string to encrypt.</param>
        /// <param name="key">The key to encrypt the string with.</param>
        /// <returns>A string representing the PC1 encrypted form of the value given.</returns>
        public static string EncryptString(string value, string key)
        {
            if (key.Length == 0)
            {
                key = _genericEncKey;
            }

            Encrypt(key.ToCharArray(), value.ToCharArray(), value.Length);
            return new string(_cry, 0, Array.IndexOf(_cry, '\0'));
        }

        /// <summary>
        /// Returns the encryption of the given string using a default key. 
        /// </summary>
        /// <param name="value">The string to encrypt.</param>
        /// <returns>A string representing the PC1 encrypted form of the value given.</returns>
        public static string EncryptString(string value)
        {
            return EncryptString(value, _genericEncKey);
        }

        /// <summary>
        /// Returns the decryption of the given string using the given key.
        /// </summary>
        /// <param name="value">The encrypted form of the string.</param>
        /// <param name="key">The key to decrypt the string with.</param>
        /// <returns>A string representing the unencrypted form of the value given.</returns>
        public static string DecryptString(string value, string key)
        {
            if (key.Length == 0)
            {
                key = _genericEncKey;
            }

            Decrypt(key.ToCharArray(), value.ToCharArray(), value.Length);
            return new string(_cry, 0, Array.IndexOf(_cry, '\0'));
        }

        /// <summary>
        /// Returns the decryption of the given string using the given key.
        /// </summary>
        /// <param name="value">The encrypted form of the string.</param>
        /// <returns>A string representing the unencrypted form of the value given.</returns>
        public static string DecryptString(string value)
        {
            return DecryptString(value, _genericEncKey);
        }
    }
}
